package com.cyx.spring.jdbc;

import cn.hutool.core.collection.ListUtil;
import com.cyx.spring.jdbc.rowmapper.RowMapper;
import com.cyx.spring.jdbc.rowmapper.RowMapperFactory;
import com.cyx.spring.tx.TransactionStatus;
import com.cyx.spring.tx.TransactionUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * JdbcTemplate
 *
 * @author yongxiang
 * @date 2024/10/24 16:44
 * @desc
 */
public class JdbcTemplate {

    private DataSource dataSource;


    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 查询obejct
     * @param sql
     * @param clazz
     * @param args
     * @return
     * @param <T>
     */
    public <T> T queryForObject(String sql, Class<T> clazz, Object... args) throws SQLException {
        return execute(preparedStatementCreator(sql,args), preparedStatement -> {
            if (preparedStatement.execute()) {
                //success
                ResultSet resultSet = preparedStatement.getResultSet();
                if (resultSet.next()) {
                    return mapperResultSet(resultSet, clazz);
                }
            }
            return null;
        });
    }

    /**
     * 查询obejct
     * @param sql
     * @param clazz
     * @param args
     * @return
     * @param <T>
     */
    public <T> List<T> queryForObjectList(String sql, Class<T> clazz, Object... args) throws SQLException {
         return execute(preparedStatementCreator(sql,args), preparedStatement -> {
             List<T> rs = new ArrayList<>();
             if (preparedStatement.execute()) {
                //success
                ResultSet resultSet = preparedStatement.getResultSet();
                while (resultSet.next()) {
                    T t = mapperResultSet(resultSet, clazz);
                    if (t != null){
                        rs.add(t);
                    }
                }
            }
            return rs;
        });
    }

    public int update(String sql, Object... args) throws SQLException {
        return execute(preparedStatementCreator(sql,args), PreparedStatement::executeUpdate);
    }

    private <T> T mapperResultSet(ResultSet resultSet, Class<T> clazz) throws Exception{
        RowMapper<T> rowMapper = RowMapperFactory.getRowMapper(clazz);
        return rowMapper.mapRow(resultSet);
    }

    public <T> T execute(PreparedStatementCreator preparedStatementCreator,PreparedStatementCallback<T> preparedStatementCallback) throws SQLException {
        return execute(con->{
            PreparedStatement preparedStatement = preparedStatementCreator.createPreparedStatement(con);
            return preparedStatementCallback.doInPreparedStatement(preparedStatement);
        });
    }

    public <T> T execute(ConnectionCallback<T> action) throws SQLException {
        // 获取新连接:
        try  {
            TransactionStatus localTanStatus = TransactionUtils.getLocalTanStatus();
            Connection connection = null;
            if (localTanStatus != null){
                connection = localTanStatus.getConnection();
                return action.doInConnection(connection);
            }else {
                try {
                    connection = dataSource.getConnection();
                    return action.doInConnection(connection);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    if (connection != null){
                        connection.close();
                    }
                }
            }

        } catch (SQLException e) {
            throw new SQLException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private PreparedStatementCreator preparedStatementCreator(String sql, Object... args) {
        return (Connection con) -> {
            var ps = con.prepareStatement(sql);
            bindArgs(ps, args);
            return ps;
        };
    }

    private void bindArgs(PreparedStatement ps, Object... args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
    }

}
